home *** CD-ROM | disk | FTP | other *** search
/ NeXT Education Software Sampler 1992 Fall / NeXT Education Software Sampler 1992 Fall.iso / Programming / Source / loTeX / loTeX.app / XText.m < prev    next >
Encoding:
Text File  |  1992-04-14  |  11.4 KB  |  458 lines

  1. /*    This file is part of the XText package (version 0.8)
  2.     Mike Dixon, April 1992
  3.     
  4.     Copyright (c) 1992 Xerox Corporation.  All rights reserved.
  5.  
  6.     Use and copying of this software and preparation of derivative works based
  7.     upon this software are permitted.  This software is made available AS IS,
  8.     and Xerox Corporation makes no warranty about the software or its
  9.     performance.
  10. */
  11.  
  12. #import "XText.h"
  13. #import <appkit/Application.h>
  14. #import <appkit/ClipView.h>
  15. #import <appkit/publicWraps.h>
  16. #import <stdio.h>
  17.  
  18. /*    Some of this code is based on other emacs-like Text classes by
  19.     Julie Zelenski, Lee Boynton, and Glen Diener.
  20.  
  21.     There's some ugly hair in here; the Text object is not very well
  22.     designed to support this kind of stuff.  No doubt this will all be
  23.     fixed by NextStep 9.0 ...
  24. */
  25.  
  26. /*    We use an undocumented (but very useful) method on ClipView; declare it
  27.     here to stop the compiler from complaining.
  28. */
  29.  
  30. @interface ClipView(undocumented)
  31. - _scrollTo:(NXPoint *)origin;
  32. @end
  33.  
  34. @implementation XText
  35.  
  36. - initFrame:(const NXRect *)frameRect text:(const char *)theText
  37.     alignment:(int)mode
  38. {
  39.     // i don't understand why the compiler whines without the (char *) here
  40.     [super initFrame:frameRect text:(char *)theText alignment:mode];
  41.     posHint = -1;
  42.     xHintPos = -1;
  43.     return self;
  44. }
  45.  
  46. - goto:(int)pos end:(int)end mode:(int)mode
  47. {
  48.     int start;
  49.     
  50.     switch(mode) {
  51.  
  52.     case 0:        // move
  53.         [self setSel:pos :pos];
  54.         [self scrollSelToVisible];
  55.         posHint = -1;
  56.         break;
  57.  
  58.     case 1:        // delete
  59.     case 2:        // cut
  60.         if (pos != end) {
  61.             start = pos;
  62.             if (start > end)
  63.                 { start = end; end = pos; }
  64.             [self disableAutodisplay];
  65.             [self setSel:start :end];
  66.             if (mode == 1)
  67.                 [self delete:self];
  68.             else
  69.                 [self cut:self];
  70.         }
  71.         posHint = -1;
  72.         break;
  73.  
  74.     case 3:        // select
  75.         start = pos;
  76.         if (start > end)
  77.             { start = end; end = pos; }
  78.         // The Text object can't even extend the selection without flashing,
  79.         // unless we disable autodisplay
  80.         if (sp0.cp != spN.cp)
  81.             [self disableAutodisplay];
  82.         [self setSel:start :end];
  83.         posHint = pos;
  84.         break;
  85.     }
  86.     xHintPos = -1;
  87.     return self;
  88. }
  89.  
  90. - moveChar:(int)cnt mode:(int)mode
  91. {
  92.     int pos, end;
  93.     int max = [self textLength];
  94.     
  95.     if (sp0.cp == posHint) {
  96.         pos = sp0.cp + cnt;
  97.         end = spN.cp;
  98.     } else {
  99.         pos = spN.cp + cnt;
  100.         end = sp0.cp;
  101.     }
  102.     if (pos < 0)
  103.         pos = 0;
  104.     else if (pos > max)
  105.         pos = max;
  106.     return [self goto:pos end:end mode:mode];
  107. }
  108.  
  109. /* NXBGetc - a text stream macro to get the previous character. */
  110.  
  111. typedef struct {
  112.     id text;
  113.     NXTextBlock *block;
  114. } textInfo;
  115.  
  116. static char getPrevious(NXStream *s)
  117. {
  118.     textInfo *info = (textInfo *) s->info;
  119.     NXTextBlock *block = info->block->prior;
  120.  
  121.     if (!block)
  122.         return EOF;
  123.     s->buf_base = block->text;
  124.     s->buf_ptr = s->buf_base + block->chars;
  125.     s->offset -= block->chars;
  126.     info->block = block;
  127.     return *(--s->buf_ptr);
  128. }
  129.  
  130. #define NXBGetc(s) \
  131.     (((s)->buf_base == (s)->buf_ptr) ? getPrevious(s) : \
  132.                                        *(--((s)->buf_ptr)) )
  133.  
  134. - moveWord:(int)cnt mode:(int)mode
  135. {
  136.     NXStream *s = [self stream];
  137.     char c;
  138.     int i, pos, end;
  139.     unsigned char digit_cat = charCategoryTable['0'];
  140.     unsigned char alpha_cat = charCategoryTable['a'];
  141.     unsigned char c_cat;
  142.     BOOL inWord = NO;
  143.  
  144.     if (cnt == 0)
  145.         return self;
  146.     if (sp0.cp == posHint) {
  147.         pos = sp0.cp;
  148.         end = spN.cp;
  149.     } else {
  150.         pos = spN.cp;
  151.         end = sp0.cp;
  152.     }
  153.     NXSeek(s, pos, NX_FROMSTART);
  154.     i = (cnt<0 ? -cnt : cnt);
  155.     while (1) {
  156.         c = (cnt<0 ? NXBGetc(s) : NXGetc(s));
  157.         if (c == EOF) break;
  158.         c_cat = charCategoryTable[c];
  159.         if (c_cat==alpha_cat || c_cat==digit_cat)
  160.             inWord = YES;
  161.         else if (inWord) {
  162.             --i;
  163.             if (i > 0)
  164.                 inWord = NO;
  165.             else
  166.                 break;
  167.         }
  168.     }
  169.     pos = NXTell(s);
  170.     if (c != EOF)
  171.         pos += (cnt<0 ? 1 : -1);
  172.     return [self goto:pos end:end mode:mode];
  173. }
  174.  
  175. /*  line is from an NXSelPt; returns the length of the line.
  176.     too bad this requires grunging in Text's data structures */
  177.  
  178. #define LINE_LENGTH(line) \
  179.     (self->theBreaks->breaks[(line)/sizeof(NXLineDesc)] & 0x3fff)
  180.  
  181. - moveLine:(int)cnt mode:(int)mode
  182. {
  183.     int pos, end, x, dir;
  184.  
  185.     if (sp0.cp == posHint) {
  186.         pos = sp0.cp;
  187.         end = spN.cp;
  188.     } else {
  189.         pos = spN.cp;
  190.         end = sp0.cp;
  191.     }
  192.     if (mode != 0)
  193.         [self disableAutodisplay];
  194.     // collapse and normalize the selection
  195.     [self setSel:pos :pos];
  196.     x = (sp0.cp == xHintPos ? xHint : (sp0.cp - sp0.c1st));
  197.     
  198.     if (cnt < 0) {
  199.         dir = NX_UP;
  200.         cnt = -cnt;
  201.     } else {
  202.         dir = NX_DOWN;
  203.     }
  204.     for (; cnt > 0; --cnt)
  205.         [self moveCaret: dir];
  206.  
  207.     pos = LINE_LENGTH(sp0.line)-1;
  208.     if (x < pos)
  209.         pos = x;
  210.     pos += sp0.c1st;
  211.     [self goto:pos end:end mode:mode];
  212.     xHintPos = pos;
  213.     xHint = x;
  214.     return self;
  215. }
  216.  
  217. - lineBegin:(int)mode
  218. {
  219.     int pos, end;
  220.  
  221.     if (sp0.cp == posHint) {
  222.         pos = sp0.c1st;
  223.         end = spN.cp;
  224.     } else {
  225.         pos = spN.c1st;
  226.         // Text is inconsistent about what line it thinks we're on
  227.         if (spN.cp == (spN.c1st + LINE_LENGTH(spN.line)))
  228.             pos = spN.cp;
  229.         end = sp0.cp;
  230.     }
  231.     return [self goto:pos end:end mode:mode];
  232. }
  233.  
  234. - lineEnd:(int)mode
  235. {
  236.     NXSelPt *sp;
  237.     int pos, end;
  238.  
  239.     if (sp0.cp == posHint) {
  240.         sp = &sp0;
  241.         end = spN.cp;
  242.     } else {
  243.         // need to correct for TBD
  244.         sp = &spN;
  245.         end = sp0.cp;
  246.     }
  247.     pos = sp->c1st + LINE_LENGTH(sp->line) - 1;
  248.     if (pos < sp->cp) {
  249.         // Text is being flakey again; we really want to be on the next line
  250.         // this is pretty gross
  251.         pos = sp->line;
  252.         if (theBreaks->breaks[pos/sizeof(NXLineDesc)] < 0)
  253.             pos += sizeof(NXHeightChange);
  254.         else
  255.             pos += sizeof(NXLineDesc);
  256.         pos = sp->cp + LINE_LENGTH(pos) - 1;
  257.     }
  258.     if ((pos == sp->cp) && (mode != 0))
  259.         ++pos;
  260.     return [self goto:pos end:end mode:mode];
  261. }
  262.  
  263. - docBegin:(int)mode
  264. {
  265.     return [self goto:0
  266.                  end:(sp0.cp == posHint ? spN.cp : sp0.cp)
  267.                  mode:mode];
  268. }
  269.  
  270. - docEnd:(int)mode
  271. {
  272.     return [self goto:[self textLength]
  273.                  end:(sp0.cp == posHint ? spN.cp : sp0.cp)
  274.                  mode:mode];
  275. }
  276.  
  277. - collapseSel:(int)dir
  278. {
  279.     int pos;
  280.  
  281.     if ((dir < 0) || ((dir == 0) && (sp0.cp == posHint)))
  282.         pos = sp0.cp;
  283.     else
  284.         pos = spN.cp;
  285.     return [self goto:pos end:pos mode:0];
  286. }
  287.  
  288. - transChars
  289. {
  290.     int pos = sp0.cp;
  291.     char buf[2], temp;
  292.  
  293.     if (pos == spN.cp) {
  294.         if (pos == (sp0.c1st + LINE_LENGTH(sp0.line) - 1))
  295.             --pos;
  296.         if (pos > 0)
  297.             if ([self getSubstring:buf start:pos-1 length:2] == 2) {
  298.                 temp = buf[1]; buf[1] = buf[0]; buf[0] = temp;
  299.                 [self disableAutodisplay];
  300.                 [self setSel:pos-1 :pos+1];
  301.                 [self replaceSel:buf length:2];
  302.                 return self;
  303.             }
  304.     }
  305.     NXBeep();
  306.     return self;
  307. }
  308.  
  309. - openLine
  310. {
  311.     int pos = sp0.cp;
  312.  
  313.     // don't do anything if there's a non-empty selection
  314.     if (pos == spN.cp) {
  315.         [self replaceSel:"\n"];
  316.         [self setSel:pos :pos];
  317.     } else
  318.         NXBeep();
  319.     return self;
  320. }
  321.  
  322. - scroll:(int)pages :(int)lines
  323. {
  324.     NXRect r;
  325.  
  326.     // if our superview isn't a ClipView, we can't scroll
  327.     if ([superview respondsTo:@selector(_scrollTo:)]) {
  328.         [superview getBounds:&r];
  329.         r.origin.y += pages*r.size.height + lines*[self lineHeight];
  330.         [superview _scrollTo:&r.origin];
  331.     } else
  332.         NXBeep();
  333.     return self;
  334. }
  335.  
  336. - scrollIfRO:(int)pages :(int)lines
  337. {
  338.     if (![self isEditable])
  339.         return [self scroll:pages :lines];
  340.     else
  341.         return nil;
  342. }
  343.  
  344. - insertChar:(NXEvent *)event
  345. {
  346.     char c;
  347.  
  348.     c = event->data.key.charCode;
  349.     [self replaceSel:&c length:1];
  350.     return self;
  351. }
  352.  
  353. - insertNextChar
  354. {
  355.     static id action = nil;
  356.  
  357.     if (!action)
  358.         action = [[XTEventMsgAction allocFromZone:[NXApp zone]]
  359.                             initSel:@selector(insertChar:)];
  360.     nextAction = action;
  361.     return self;
  362. }
  363.  
  364. @end
  365.  
  366. /*    A (not very elegant) table format for storing the initial emacs bindings.
  367.     Unused args are indicated by the magic value 99.
  368. */
  369.  
  370. typedef struct {
  371.     SEL   *sel;
  372.     short arg1;
  373.     short arg2;
  374.     keyCode key;
  375. } tbl_entry;
  376.  
  377. /*    For these and other key codes, refer to
  378.     /NextLibrary/Documentation/NextDev/Summaries/06_KeyInfo/KeyInfo.rtfd
  379. */
  380.  
  381. const tbl_entry emacs_base[] = {
  382. {&@selector(moveChar:mode:), -1,  0, 0x351},  // ctrl-b       move back char
  383. {&@selector(moveChar:mode:), -1,  1, 0x401},  // ctrl-h       delete back char
  384. {&@selector(moveChar:mode:), -1,  3, 0x353},  // ctrl-B       select back char
  385. {&@selector(moveChar:mode:),  1,  0, 0x3c1},  // ctrl-f       move fwd char
  386. {&@selector(moveChar:mode:),  1,  1, 0x3b1},  // ctrl-d       delete fwd char
  387. {&@selector(moveChar:mode:),  1,  3, 0x3c3},  // ctrl-F       select fwd char
  388. {&@selector(moveWord:mode:), -1,  0, 0x354},  // alt-b       move back word
  389. {&@selector(moveWord:mode:), -1,  1, 0x1b4},  // alt-del   delete back word
  390. {0,                              0,  0, 0x404},  // alt-h            (ditto)
  391. {&@selector(moveWord:mode:), -1,  3, 0x356},  // alt-B       select back word
  392. {&@selector(moveWord:mode:),  1,  0, 0x3c4},  // alt-f       move fwd word
  393. {&@selector(moveWord:mode:),  1,  1, 0x3b4},  // alt-d       delete fwd word
  394. {&@selector(moveWord:mode:),  1,  3, 0x3c6},  // alt-F       select fwd word
  395. {&@selector(moveLine:mode:), -1,  0, 0x081},  // ctrl-p       move back line
  396. {&@selector(moveLine:mode:), -1,  3, 0x083},  // ctrl-P       select back line
  397. {&@selector(moveLine:mode:),  1,  0, 0x371},  // ctrl-n       move fwd line
  398. {&@selector(moveLine:mode:),  1,  3, 0x373},  // ctrl-N       select fwd line
  399. {&@selector(lineBegin:),      0, 99, 0x391},  // ctrl-a       move to line begin
  400. {&@selector(lineBegin:),      3, 99, 0x393},  // ctrl-A       select to line bgn
  401. {&@selector(lineEnd:),          0, 99, 0x441},  // ctrl-e       move to line end
  402. {&@selector(lineEnd:),          1, 99, 0x3e1},  // ctrl-k       delete to line end
  403. {&@selector(lineEnd:),          3, 99, 0x443},  // ctrl-E       select to line end
  404. {&@selector(docBegin:),          0, 99, 0x2e6},  // alt-<       move to doc begin
  405. {&@selector(docEnd:),          0, 99, 0x2f6},  // alt->       move to doc begin
  406. {&@selector(collapseSel:),      0, 99, 0x381},  // ctrl-spc  collapse selection
  407. {&@selector(transChars),     99, 99, 0x481},  // ctrl-t    transpose chars
  408. {&@selector(setNextAction:),  0, 99, 0x421},  // ctrl-q       quote next key
  409. {&@selector(insertNextChar), 99, 99, 0x425},  // ctrl-alt-q   really quote key
  410. {&@selector(openLine),         99, 99, 0x071},  // ctrl-o       open line
  411. {&@selector(scroll::),          1, -1, 0x341},  // ctrl-v       scroll fwd page
  412. {0,                              0,  0, 0x0f6},  // alt-shft-down    (ditto)
  413. {&@selector(scroll::),         -1,  1, 0x344},  // alt-v       scroll back page
  414. {0,                              0,  0, 0x166},  // alt-shft-up    (ditto)
  415. {&@selector(scroll::),          0,  4, 0x343},  // ctrl-V       scroll fwd 4 lines
  416. {&@selector(scroll::),          0, -4, 0x346},  // alt-V       scroll back 4 lines
  417. {&@selector(scroll::),      -9999,  0, 0x165},  // alt-ctrl-up    scroll to start
  418. {&@selector(scroll::),       9999,  0, 0x0f5},  // alt-ctrl-down  scroll to end
  419. {&@selector(scrollIfRO::),      1, -1, 0x380},  // space       scroll fwd pg if RO
  420. {&@selector(scrollIfRO::),     -1,  1, 0x1b0},  // del       scroll back pg if RO
  421. {&@selector(scrollIfRO::),      0,  4, 0x382},  // shift-sp  scroll fwd 4 lines
  422. {&@selector(scrollIfRO::),      0, -4, 0x1b2},  // shift-del scroll back 4 lines
  423. {&@selector(scrollSelToVisible),
  424.                              99, 99, 0x2d1},  // ctrl-l       scroll to selection
  425. {0, 0, 0}
  426. };
  427.  
  428. void initbase_emacs(actionTbl actions, NXZone *zone)
  429. {
  430.     keyCode i;
  431.     const tbl_entry *e;
  432.     XTAction *a = [XTAction undefinedAction];
  433.  
  434.     // make all non-command control & alt combinations invoke "unboundKey"
  435.     for (i=0; i<KEY_CODES; i+=16) {
  436.         actions[i+1] = actions[i+3] = actions[i+4] = actions[i+5]
  437.             = actions[i+6] = actions[i+7] = a;
  438.     }
  439.  
  440.     // ... except for ctrl-i (a handy substitute for tab)
  441.     actions[6*16 + 1] = nil;
  442.  
  443.     // and then install the emacs key bindings
  444.     for (e=emacs_base; (e->key != 0); ++e) {
  445.         if (e->sel == 0) {}
  446.             // same action as previous binding
  447.         else if (e->arg1 == 99)
  448.             a = [[XTMsg0Action allocFromZone:zone] initSel:*(e->sel)];
  449.         else if (e->arg2 == 99)
  450.             a = [[XTMsg1Action allocFromZone:zone]
  451.                     initSel:*(e->sel) arg:e->arg1];
  452.         else
  453.             a = [[XTMsg2Action allocFromZone:zone]
  454.                     initSel:*(e->sel) arg:e->arg1 arg:e->arg2];
  455.         actions[e->key] = a;
  456.     }
  457. }
  458.